// -------------------------------------------------------------------------------------
// ImagePortfolio.m - Visual TIFF/EPS file manager
// Author: Martin D. Flynn
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.
// -------------------------------------------------------------------------------------

#import <libc.h>
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <mach/mach.h>
#import <objc/List.h>
#import <objc/objc-runtime.h>
#import <objc/NXBundle.h>
#import <defaults/defaults.h>
#import <dpsclient/wraps.h>
#import	<appkit/Window.h>
#import <appkit/Panel.h>
#import <appkit/OpenPanel.h>
#import <appkit/Text.h>
#import <appkit/TextField.h>
#import <appkit/Button.h>
#import <appkit/Matrix.h>
#import <appkit/Font.h>
#import <appkit/ScrollView.h>
#import <appkit/Speaker.h>
#import <appkit/Listener.h>
#import	<appkit/Menu.h>
#import	<appkit/MenuCell.h>
#import <appkit/graphics.h>
#import <appkit/publicWraps.h>
#import <appkit/NXImage.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/NXEPSImageRep.h>
#import "GifImageRep.h"
#import "ImagePortfolio.h"
#import "Portfolio.h"
#import "PaletteCell.h"		// for adding support image types
#import "PaletteMatrix.h"

// -------------------------------------------------------------------------------------
// version number
#define	versionNUMBER		"1.30"

// -------------------------------------------------------------------------------------
// This is done for the purpose of allowing to setDelegate on the appIcon window
// [DelegateWindow poseAs:[Window class]] must be issued in +new(Application) 
@interface DelegateWindow:Window @end
@implementation DelegateWindow
- setDelegate:sender
{
	if ((wFlags.style != NX_TOKENSTYLE) && (wFlags.style != NX_MINIWORLDSTYLE)) {
		[super setDelegate:sender];
	} else {
		int saveStyle = wFlags.style;
		wFlags.style = NX_PLAINSTYLE;
		[super setDelegate:sender];
		wFlags.style = saveStyle;
	}
	return self;
}
@end

// -------------------------------------------------------------------------------------
@implementation ImagePortfolio

// -------------------------------------------------------------------------------------
// menu/button actions

/* make and show new image Palette */
- newImagePalette:sender
{
	return [[Portfolio alloc] initFromFile:(char*)nil];
}

/* open Palette file */
- open:sender
{
	id		pOpen;
	char	fName[MAXPATHLEN+1], *dir, **files, *fTypes[] = { docEXTENSION, 0 };

	/* build/display open panel */
	pOpen = [OpenPanel new];
	[pOpen setTitle:"Open Image Portfolio"];
	[pOpen setPrompt:"File:"];
	[pOpen setRequiredFileType:fTypes[0]];
	[pOpen allowMultipleFiles:YES];
	[pOpen setDirectory:[NXApp lastPath]];
	if (![pOpen runModalForTypes:fTypes]) return self;

	/* open the list of selected files */
	if (!(files = (char**)[pOpen filenames])) return self;
	if (dir=(char*)[pOpen directory]) { chdir(dir); [self setLastPath:dir]; }
	while (*files) {
		sprintf(fName, "%s/%s", dir, *files++);
		[[Portfolio alloc] initFromFile:fName];
	}

	return self;
}

/* arrange windows in front */
- arrangeInFront:sender
{
	return self;
}

/* show info panel */
- showInfo:sender
{
	return [infoPanel makeKeyAndOrderFront:sender];
}

/* show help panel */
- showHelp:sender
{
	return [helpPanel makeKeyAndOrderFront:sender];
}

/* show preferences panel (to be called by PaletteMatrix only!) */
- showPreferences:sender
{
	id	winDel = [Portfolio activePortfolio];
	int	rows, cols;
  
	/* exit if not proper portfolio type (must match sender) */
	if (!winDel || ![winDel isKindOf:[Portfolio class]]) return self;
  
	/* set screen fields */
	[fontName setStringValue:[[winDel font] name]];
	[fontSize setFloatValue:[[winDel font] pointSize]];
	[prefCellWidth  setFloatValue:[winDel cellSize]->width ];
	[prefCellHeight setFloatValue:[winDel cellSize]->height];
	[winDel getDisplayedRows:&rows cols:&cols];
	[prefCellRows setIntValue:rows];
	[prefCellColumns setIntValue:cols];
  
	/* show window */
	[prefPanel makeKeyAndOrderFront:sender];
	[NXApp runModalFor:prefPanel];
  
	return self;
}

/* cancel preferences */
- cancelPreferences:sender
{
	[NXApp stopModal:0];
	[prefPanel orderOut:sender];
	return self;
}

/* save preference to defaults table */
- saveDefaults:sender
{
	id	winDel = [Portfolio activePortfolio];
	if (winDel && [winDel isKindOf:[Portfolio class]]) [winDel saveDefaults:sender];
	return self;
}

/* print key window */
- printKeyWindow:sender
{
	return (keyWindow)? [keyWindow smartPrintPSCode:sender] : (id)nil;
}

// -------------------------------------------------------------------------------------
// return application constants

/* set last default saved/openned file path */
- setLastPath:(char*)path
{
	return [self setLastPathNoCopy:NXCopyStringBuffer(path)];
}

/* set last default saved/openned file path */
- setLastPathNoCopy:(char*)path
{
	if (lastPath) free(lastPath);
	lastPath = path;
	return self;
}

/* return last saved/openned path */
- (const char*)lastPath
{
	return (lastPath)? lastPath : NXHomeDirectory();
}

// -------------------------------------------------------------------------------------
// inline event dispatcher
// - When this method is interleaved with other methods, such as loading a large image
//   directory, this allows continued interaction with the user.
- dispatchEvents
{
	NXEvent	*e;
	while (e=[NXApp getNextEvent:NX_MOUSEDOWNMASK waitFor:0.0 threshold:NX_BASETHRESHOLD])
		[NXApp sendEvent:e];
	return self;
}
  
// -------------------------------------------------------------------------------------
// menu cell update handler methods

- _setMenuCellsUpdateAction:menuId
{
	id	cellList = [[menuId itemList] cellList];
	int	i = [cellList count];
	while(i) {
		id cell = [cellList objectAt:--i];
		SEL	updAct, cSel = [cell action];
		if ([cell hasSubmenu]) { [self _setMenuCellsUpdateAction:[cell target]]; continue; }
		if ([cell target]) continue;
		updAct = @selector(_ucKeyWindow:);	// default updateAction
		if (cSel==@selector(cut:)               ) updAct=@selector(_ucCutCopyDelete:); else
		if (cSel==@selector(copy:)			    ) updAct=@selector(_ucCutCopyDelete:); else
		if (cSel==@selector(delete:)            ) updAct=@selector(_ucCutCopyDelete:); else
		if (cSel==@selector(undelete:)		    ) updAct=@selector(_ucUndelete:);      else
		if (cSel==@selector(paste:)			    ) updAct=@selector(_ucResponder:);     else
		if (cSel==@selector(save:)			    ) updAct=@selector(_ucSelectable:);    else
		if (cSel==@selector(saveAs:)            ) updAct=@selector(_ucSelectable:);    else
		if (cSel==@selector(print:)			    ) updAct=@selector(_ucSelectable:);    else
		if (cSel==@selector(selectAll:)		    ) updAct=@selector(_ucSelectable:);    else
		if (cSel==@selector(sortByCellTitle:)   ) updAct=@selector(_ucSelectable:);    else
		if (cSel==@selector(showPreferences:)   ) updAct=@selector(_ucMainWindow:);    else
		if (cSel==@selector(performClose:)	    ) updAct=@selector(_ucKeyWindow:);     else
		if (cSel==@selector(performMiniaturize:)) updAct=@selector(_ucKeyWindow:);     else
		if (cSel==@selector(printKeyWindow:)    ) updAct=@selector(_ucKeyWindow:);
		[cell setUpdateAction:updAct forMenu:menuId];
		[menuId setAutoupdate:YES];
	}
	return self;
}

/* return menuCell responder */
- _cellResponder:cellId
{
	id	respId = (keyWindow)? [keyWindow firstResponder] : (id)nil;
	return (respId && [respId respondsTo:[cellId action]])? respId : (id)nil;
}

/* return menuCell enabling flag */
- (BOOL)_cellEnabler:cellId :(SEL)meth
{
	id	respId = [self _cellResponder:cellId];
	[cellId setEnabled:((respId&&[respId respondsTo:meth]&&[respId perform:meth])?YES:NO)];
	return YES;
}

/* print/save/saveAs/selectAll: cells selectable */
- (BOOL)_ucSelectable:cellId
{
	return [self _cellEnabler:cellId :@selector(anySelectableCells)];
}

/* cut/copy/delete */
- (BOOL)_ucCutCopyDelete:cellId
{
	return [self _cellEnabler:cellId :@selector(selectedCell)];
}

/* undelete */
- (BOOL)_ucUndelete:cellId
{
	return [self _cellEnabler:cellId :@selector(undeletable)];
}

/* keyWindow menu cell update method */
- (BOOL)_ucKeyWindow:cellId
{
	[cellId setEnabled:(keyWindow?YES:NO)];
	return YES;
}

/* mainWindow menu cell update method */
- (BOOL)_ucMainWindow:cellId
{
	[cellId setEnabled:(mainWindow?YES:NO)];
	return YES;
}

/* paste */
- (BOOL)_ucResponder:cellId
{
	[cellId setEnabled:([self _cellResponder:cellId]?YES:NO)];
	return YES;
}

// -------------------------------------------------------------------------------------
// application delegate

/* Application instantiation */
+ new
{
	[DelegateWindow poseAs:[Window class]];
	return [super new];
}

/* application initialization */
- appDidInit:sender
{
    NXAtom	pbsTypes[1];

	/* clear vars */
	lastPath = (char*)nil;
	
	
	/* set the first responder update actions */
	[self setAutoupdate:YES];
	[self _setMenuCellsUpdateAction:[NXApp mainMenu]];

	/* set version number */
	[versionNum setStringValue:versionNUMBER];

	/* add supported image file types */
	[PaletteCell addImageClass:[GifImageRep class]		, ".gif"              , (char*)nil];
	[PaletteCell addImageClass:[NXBitmapImageRep class]	, ".tiff", ".icon"    , (char*)nil];
	[PaletteCell addImageClass:[NXEPSImageRep class]	, ".eps", ".ps", ".ai", (char*)nil];
  
	/* center information panel */
	[infoPanel center];
	[helpPanel center];
	[prefPanel center];

	/* make app active */
	[NXApp activateSelf:YES];
  
	/* find real appIcon local window number and register for dragging */
	[[NXApp appIcon] setDelegate:self];
	NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &appIconWinNum);
    NXConvertGlobalToWinNum(appIconWinNum, &appIconWinNum);
    pbsTypes[0] = NXFilenamePboardType;
    [[NXApp appIcon] registerForDraggedTypes:pbsTypes count:1];

	return self;
}

/* application termination (check windows for unsaved files) */
- appWillTerminate:sender
{
	
	/* display panel if unsaved files are found */
	if ([Portfolio isDocEdited]) {
		int rtn;
		const char *msg, *quit, *qAny, *cncl;
		msg  = NXLocalizedString("There are edited windows.", (char*)nil,(char*)nil);
		quit = NXLocalizedString("Quit", (char*)nil,(char*)nil);
		qAny = NXLocalizedString("Quit Anyway", (char*)nil,(char*)nil);
		cncl = NXLocalizedString("Cancel", (char*)nil,(char*)nil);
		rtn = NXRunAlertPanel(quit, msg, qAny, cncl, (char*)nil);
		if (rtn == NX_ALERTALTERNATE) return (id)nil;
	}
	
	/* MUST unregister appIcon window, otherwise drag may fail next time app is launched */
	[[NXApp appIcon] unregisterDraggedTypes];
	
	/* return */
	return self;
}

/* indicate to Workspace Manager that I accept files */
- (BOOL)appAcceptsAnotherFile:sender { return YES; }

/* open a Workspace Manager provided file */
- (int)appOpenFile:(char*)filename type:(char*)type
{
	[[Portfolio alloc] initFromFile:filename];
	return YES;
}

// -------------------------------------------------------------------------------------
// application icon window delegate
// - This section handles files dragged and dropped onto the application icon
static char		iconFiles[MAXPATHLEN + 1] = { 0 };

/* set dock icon to specified image */
- _setDockIcon:image
{
	id				iconWin = [NXApp appIcon];	
	NXPoint			iconOrigin = { 0.0, 0.0 };
	NXSize			imageSize;
	static id		tileImage = (id)nil;
	static NXSize	tileSize = { 0.0, 0.0 };
  
	/* init dock icon tile */
	if (!tileImage && (tileImage = [NXImage findImageNamed:"NXAppTile"])) {
		[tileImage getSize:&tileSize];
	}

	/* draw background tile */
	[[iconWin contentView] lockFocus];
	PSsetinstance(FALSE);
	[tileImage composite:NX_COPY toPoint:&iconOrigin];
	[tileImage getSize:&tileSize];
	[image getSize:&imageSize];
	iconOrigin.x = (tileSize.width  - imageSize.width ) / 2.0;
	iconOrigin.y = (tileSize.height - imageSize.height) / 2.0;
	[image composite:NX_SOVER toPoint:&iconOrigin];
	[[iconWin contentView] unlockFocus];
	[iconWin display];
  
	return self;
}

/* files entered icon window */
// - save the list of file names and the representing icon image
- (NXDragOperation)draggingEntered:sender
{
	char		*fileList;
	int			fileListLen;
    Pasteboard	*pb = [sender draggingPasteboard];
	static id	openFolder = (id)nil;
	if (!openFolder) openFolder = [NXImage findImageNamed:"openFolder"];
	if ([pb readType:NXFilenamePboardType data:&fileList length:&fileListLen]) {
		strcpy(iconFiles, fileList);
		[self _setDockIcon:openFolder];
		return NX_DragOperationCopy;
	}
    return NX_DragOperationNone;
}

/* mouse exited icon window */
- draggingExited:sender
{
	static id	dockIcon = (id)nil;
	if (!dockIcon) dockIcon = [NXImage findImageNamed:"app"];
	[self _setDockIcon:dockIcon];
	return self;
}

/* indicate we accept dragging */
- (BOOL)prepareForDragOperation:sender
{
	if (!*iconFiles) return NO;
    return YES;
}

/* files dropped in icon window */
- (BOOL)performDragOperation:sender
{
	if (!*iconFiles) return NO;
	[NXApp activateSelf:YES];
	[[Portfolio alloc] initFromFile:iconFiles];
	[self draggingExited:(id)nil];
	return YES;
}

/* find window for number (special case for appIcon window) */
- findWindow:(int)windowNum
{
	return (windowNum == appIconWinNum)? [NXApp appIcon] : [super findWindow:windowNum];
}

/*
- (int)dragEnter:(int)windowNum x:(double)x y:(double)y iconOffsetX:(double)hx iconOffsetY:(double)hy iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY iconWidth:(double)iconWidth iconHeight:(double)iconHeight sequenceNumber:(int)seqNum pasteboardName:(char *)pbName operationMask:(int)opMask operation:(int *)op
{
	int gWin;
    NXConvertGlobalToWinNum(windowNum, &gWin);
	printf("dragEnter...(%d/%d)(%d/%d)\n", windowNum, gWin,
		appIconWinNum, [[NXApp appIcon] windowNum]);
	return (int)[super dragEnter:windowNum x:x y:y iconOffsetX:hx iconOffsetY:hy iconWindow:iconWindowNum iconX:iconX iconY:iconY iconWidth:iconWidth iconHeight:iconHeight sequenceNumber:seqNum pasteboardName:pbName operationMask:opMask operation:op];
}
*/

@end
